package drds.plus.parser.lexer;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * <pre>
 * --金刚经
 * 事无利无过细，视前尘如梦境，清净为天下福。
 * 刚则折，柔恒存，柔于甚刚强。
 * 无欲无求，淡薄之至，终能疾雷过山而不惊，白刃交前而不惧。
 * 气沉丹田，中正安舒，呼吸要连绵，深入丹田。
 * 意行气行，意到气到，静中有动，动中有静。
 *
 * --无论我多用力打球，都会被水弹回来。遇强则强。想不到我连水都打不过。【气沉丹田，中正安舒】
 * --保持重心,借力用力。一个球转动就可以把周围的东西弹开，但本身很稳定。【静中有动，动中有静】
 *
 * --太极
 * 刀光剑影不是我门派，天空海阔自有我风采，双手一推非黑也非白，不好也不坏，没有胜又何来败，没有去，哪有来，手中无剑心中无尘，才是我胸怀，随缘而去乘风而来，才是我胸怀。
 * </pre>
 */
public class Lexer {

    private final static byte EOI = 0x1A;
    protected final char[] chars;
    protected final int eofIndex;
    protected KeyWords keyWords = KeyWords.DEFAULT_KEY_WORDS;
    protected int currentIndex = -1;
    protected char aChar;
    private Token token;
    private String stringValue;
    private int parameterIndex = 0;

    public Lexer(String chars) {
        this(fromSql2Chars(chars));
    }

    public Lexer(char[] chars) {
        this.chars = new char[chars.length + 1];
        System.arraycopy(chars, 0, this.chars, 0, chars.length);
        this.eofIndex = this.chars.length - 1;
        this.chars[this.eofIndex] = Lexer.EOI;
        scanChar();
        nextToken();
    }

    private static char[] fromSql2Chars(String sql) {
        char[] chars = new char[sql.length() + 1];
        sql.getChars(0, sql.length(), chars, 0);
        chars[chars.length - 1] = ' ';
        return chars;
    }

    public final Token token() {
        return token;
    }

    protected final char scanChar() {
        return aChar = chars[++currentIndex];
    }

    /**
     * @param skip if 1, then equals to {@link #scanChar()}
     */
    protected final char scanChar(int skip) {
        return aChar = chars[currentIndex += skip];
    }

    public final int getCurrentIndex() {
        return this.currentIndex;
    }

    protected final boolean eof() {
        return currentIndex >= eofIndex;
    }

    /**
     * @return start from 1. When there is no parameter yet, return 0.
     */
    public int parameterIndex() {
        return parameterIndex;
    }

    public Token nextToken() {
        if (token == Token.end_of_sql) {
            throw new RuntimeException("end_of_sql for chars is already reached, cannot get new token");
        }
        skip();
        Token token = nextTokenInternal();
        return token;
    }

    protected void skip() {
        for (; !eof() && CharTypes.isWhitespace(aChar); scanChar()) {
        }
    }

    private Token nextTokenInternal() {
        switch (aChar) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                scanNumber();
                return token;
            case '.':
                scanChar();
                token = Token.PUNC_DOT;
                return token;
            case '\''://只支持简单字符串,复杂字符串请使用?代替
                scanString();
                return token;

            case '(':
                scanChar();
                token = Token.PUNC_LEFT_PAREN;
                return token;
            case ')':
                scanChar();
                token = Token.PUNC_RIGHT_PAREN;
                return token;

            case ',':
                scanChar();
                token = Token.PUNC_COMMA;
                return token;
            case ';':
                scanChar();
                token = Token.PUNC_SEMICOLON;
                return token;

            case '=':
                scanChar();
                token = Token.OP_EQUALS;
                return token;
            case '?':
                scanChar();
                token = Token.QUESTION_MARK;
                ++parameterIndex;
                return token;
            case '+':
                scanChar();
                token = Token.OP_PLUS;
                return token;
            case '-':
                if (CharTypes.isDigit(chars[currentIndex + 1])) {
                    scanNumber();
                    return token;
                } else {
                    scanChar();
                    token = Token.OP_MINUS;
                    return token;
                }
            case '*':
                scanChar();
                token = Token.OP_ASTERISK;
                return token;
            case '/':
                scanChar();
                token = Token.OP_SLASH;
                return token;
            case '%':
                scanChar();
                token = Token.OP_PERCENT;
                return token;
            case '!':
                if (chars[currentIndex + 1] == '=') {
                    scanChar(2);
                    token = Token.OP_NOT_EQUALS;
                    return token;
                }
                scanChar();
                token = Token.OP_EXCLAMATION;
                return token;
            case '>':
                switch (chars[currentIndex + 1]) {
                    case '=':
                        scanChar(2);
                        token = Token.OP_GREATER_OR_EQUALS;
                        return token;
                    default:
                        scanChar();
                        token = Token.OP_GREATER_THAN;
                        return token;
                }
            case '<':
                switch (chars[currentIndex + 1]) {
                    case '=':
                        scanChar(2);
                        token = Token.OP_LESS_THAN_OR_EQUALS;
                        return token;
                    default:
                        scanChar();
                        token = Token.OP_LESS_THAN;
                        return token;
                }
            default:
                if (CharTypes.firstCharIsIdentifier(aChar)) {
                    scanIdentifier();
                } else if (eof()) {
                    token = Token.end_of_sql;
                    currentIndex = eofIndex;
                } else {
                    throw error("unsupported character: " + aChar);
                }
                return token;
        }
    }


    protected void scanString() {
        if (aChar == '\'') {
        } else {
            throw error("first char must be \" or '");
        }
        stringValue = null;
        StringBuilder sb = new StringBuilder();
        while (true) {
            scanChar();
            if (aChar == '\'') {
                scanChar();//往后面跳一个，跳过\'
                break;
            } else {
                if (eof()) {
                    throw error("unclosed fuzzy_matching");
                }
                sb.append(aChar);
                continue;
            }
        }
        stringValue = sb.toString();
        token = Token.LITERAL_CHARS;
    }

    protected void scanNumber() {
        //开头必须是数字和-
        if (!(CharTypes.isDigit(aChar) || aChar == '-')) {
            error("必须是数字和-");
        }
        //
        StringBuilder sb = new StringBuilder();
        sb.append(aChar);
        while (!eof()) {
            scanChar();
            if (CharTypes.isDigit(aChar)) {
                sb.append(aChar);
            } else {
                break;
            }
        }
        if (aChar == '.') {
            sb.append(aChar);
            while (!eof()) {
                scanChar();
                if (CharTypes.isDigit(aChar)) {
                    sb.append(aChar);
                } else {
                    break;
                }
            }
            stringValue = sb.toString();
            token = Token.LITERAL_NUM_DECIMAL;
        } else {
            stringValue = sb.toString();
            token = Token.LITERAL_NUM_INTEGER;
        }
    }

    protected void scanIdentifier() {
        stringValue = null;
        StringBuilder sb = new StringBuilder();
        sb.append(aChar);
        while (true) {
            scanChar();
            if (CharTypes.isIdentifier(aChar)) {
                sb.append(aChar);
            } else {
                if (eof()) {
                    throw error("unclosed fuzzy_matching");
                }
                break;
            }
        }
        stringValue = sb.toString();
        Token token = keyWords.getKeyword(stringValue);
        this.token = token == null ? Token.IDENTIFIER : token;
    }


    public final String stringValue() {
        return stringValue;
    }

    /**
     * {@link #token} must be {@link Token#LITERAL_NUM_INTEGER}
     */
    public Number integerValue() {
        return new BigInteger(stringValue);
    }

    public BigDecimal decimalValue() {
        return new BigDecimal(stringValue);
    }

    /**
     * always throw SQLSyntaxErrorException
     */
    protected RuntimeException error(String msg) {
        String errMsg = msg + ". " + toString();
        throw new RuntimeException(errMsg);
    }

    @Override
    public String toString() {
        StringBuilder sql = new StringBuilder();
        for (int i = 0; i < chars.length; i++) {
            if (i < currentIndex) {
                sql.append(chars[i]);
            }
            if (i == currentIndex) {
                sql.append("[正在解析在这]");
            }
            if (i > currentIndex) {
                sql.append(chars[i]);
            }
        }
        return "Lexer{" +
                "  sql长度=" + chars.length +
                "  eofIndex=" + eofIndex +
                ", currentIndex:" + currentIndex +
                ", aChar:" + aChar +
                ", token:" + token +
                ", stringValue:'" + stringValue + '\'' +
                ", parameterIndex:" + parameterIndex +
                "\n\t\r sql_process:" + sql +//
                '}';
    }
}
